#include "Lambert.h"
#include "Ray.h"
#include "Scene.h"
#include "Vector3.h"

Lambert::Lambert(const Vector3 & kd, const Vector3 & ka) :
    m_kd(kd), m_ka(ka)
{

}

Lambert::~Lambert()
{
}
Vector3 Lambert::shade(const Ray& ray, const HitInfo& hit, char rayCount, const Scene& scene, int mode, Vector3 rayColor, bool diffuseRay) const
{
    Vector3 L = Vector3(0.0f, 0.0f, 0.0f);
    
    ////////////// create measurement point
    if (mode == APPM_MEAS && rayCount < MAX_REFLECTIONS)
    {
    	HPoint *hp = new HPoint;
    	hp->isLight = false;
    	hp->f = m_kd * rayColor;
    	hp->pos = hit.P;
    	hp->norm = hit.N;
    	hp->pix = g_pixel_index;
    	g_hitpoints = g_hitpoints->ListAdd(hp);

    	return L;
    }


	////////////// compute direct diffuse lighting
	
	const Vector3 viewDir = -ray.d; // d is a unit vector
    
    
	// compute lighting from point lights
	const Lights *lightlist = scene.lights();
	Lights::const_iterator lightIter;
	for (lightIter = lightlist->begin(); lightIter != lightlist->end(); lightIter++)
	{
		PointLight* pLight = *lightIter;
    
		Vector3 l = pLight->position() - hit.P;
        
		// the inverse-squared falloff
		float falloff = l.length2();
        
		// normalize the light direction
		l /= sqrt(falloff);

		// get the diffuse component
		float nDotL = dot(hit.N, l);
		Vector3 result = pLight->color();
		result *= m_kd;
        
		L += std::max(0.0f, nDotL * pLight->wattage() / (4*PI*falloff)) * result;
	}
		
	// compute lighting from area lights
	const AreaLights *alightlist = scene.areaLights();
	AreaLights::const_iterator alightIter;
	AreaLight* pALight;
	for (alightIter = alightlist->begin(); alightIter != alightlist->end(); alightIter++)
	{
		pALight = *alightIter;
		L += m_kd * pALight->sampleLight(hit, scene);
	}
	
	

	/////////////////// add the ambient component

	L += m_ka;


	////////////////// compute indirect lighting
	if (mode == PATHTRACE && rayCount < MAX_REFLECTIONS)
	{
		// then trace a random path
		if(rayCount < MAX_REFLECTIONS)
		{
			Ray newRay(hit.P, Material::randDir(hit.N, ray));
			HitInfo newHitInfo;
			if (scene.trace(newHitInfo, newRay, epsilon))
			{
				Vector3 QQ = newHitInfo.material->shade(newRay, newHitInfo, rayCount+1, scene, mode, rayColor, true);
				L += m_kd * QQ;
				
			}
		}
	}	

    return L;
}


void Lambert::handlePhoton(const Ray& ray, const HitInfo& hit, char rayCount, Vector3 photonFlux, const Scene& scene, int mode, LightPath *lp) const
{
    if (mode == APPM_PHOTON && rayCount < MAX_REFLECTIONS)
    {
    	// from Toshiya's PPM code
		Vector3 hh = (hit.P - g_hpbbox.min) * g_hash_s;
		int ix = abs(int(hh.x)), iy = abs(int(hh.y)), iz = abs(int(hh.z));
		// strictly speaking, we should use #pragma omp critical here
		{
			List* hp = g_hash_grid[scene.hash(ix, iy, iz)];
			while (hp != NULL)
			{
				HPoint *measPt = hp->id;
				hp = hp->next;
				Vector3 v = measPt->pos - hit.P;

				if (dot(ray.d, measPt->norm) < -epsilon && (dot(measPt->norm, hit.N) > epsilon) &&
						(v.length2() <= measPt->r2))
				{
					double g = (measPt->n * ALPHA + ALPHA) / (measPt->n * ALPHA + 1.0);
					measPt->r2 = measPt->r2 * g;
					measPt->n++;
					measPt->flux = (measPt->flux + measPt->f * photonFlux * (1. / PI)) * g;
				}
			}
		}
		Vector3 f = m_kd;
    	double p = f.x > f.y && f.x > f.z ? f.x : f.y > f.z ? f.y : f.z;
		if (dot(ray.d, hit.N) < -epsilon && Rand::getRand() < p) // russian roullette
		{
			// then trace a random path
			//Ray newRay(hit.P, Material::randDir(hit.N, ray));
			float u1, u2;
			if (lp)
				lp->getDirVars(rayCount, u1, u2);
			else
			{
				u1 = Rand::getRand();
				u2 = Rand::getRand();
			}
			Ray newRay(hit.P, Rand::getRandDir(hit.N, u1, u2));
			HitInfo newHitInfo;
			if (scene.trace(newHitInfo, newRay, epsilon))
			{
				newHitInfo.material->handlePhoton(newRay, newHitInfo, rayCount+1, m_kd * photonFlux * (1./p), scene, mode, lp);
			}
		}
    }
}
